@decidio/sdk 0.3.0 → 0.3.5

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/dist/signing.d.ts CHANGED
@@ -74,6 +74,10 @@ export declare function assertJsonSafe(value: unknown, path?: string): void;
74
74
  * rule is about the WIRE, not which credential (the interactive sign-in enforced this for
75
75
  * session tokens while agent-token requests sailed through). localhost/127.0.0.1/[::1] stay
76
76
  * allowed for local dev; https always allowed. Throws before anything is sent. */
77
+ /** The URL as it may be ECHOED in a refusal: userinfo masked. The SDK treats URL userinfo as a credential
78
+ * (it is refused as one), so no refusal may print it - every branch of assertSafeTransport echoes through
79
+ * this, not the raw string (invariant-guard, 0.3.5). */
80
+ export declare function redactUrl(u: string): string;
77
81
  export declare function assertSafeTransport(url: string, hasCredential: boolean): void;
78
82
  /** The ONE answer to "does this request carry a credential worth protecting?" (A6, 0.1.12).
79
83
  * A signed identity proof is a credential too: sending it over remote plaintext disclosed
package/dist/signing.js CHANGED
@@ -106,7 +106,17 @@ export function assertJsonSafe(value, path = "args") {
106
106
  };
107
107
  walk(value, path);
108
108
  }
109
+ export function redactUrl(u) { return u.replace(/\/\/[^/@]*@/, "//***@"); }
109
110
  export function assertSafeTransport(url, hasCredential) {
111
+ if (typeof url !== "string") {
112
+ throw new Error(`[decidio] the gate URL is ${url === null ? "null" : typeof url}, not a string, so there is no host to send to. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
113
+ }
114
+ for (const ch of url) {
115
+ const cp = ch.codePointAt(0);
116
+ if (cp <= 0x20 || (cp >= 0x7f && cp <= 0x9f) || cp === 0xfeff) {
117
+ throw new Error(`[decidio] the gate URL ${JSON.stringify(redactUrl(url))} contains whitespace or a control character (U+${cp.toString(16).toUpperCase().padStart(4, "0")}) - a validator and a transport can read such a spelling differently, so it is refused as written. Remove it. Nothing was sent.`);
118
+ }
119
+ }
110
120
  let u;
111
121
  try {
112
122
  u = new URL(url);
@@ -114,9 +124,9 @@ export function assertSafeTransport(url, hasCredential) {
114
124
  catch {
115
125
  const spelled = url.replace(/[\t\r\n]/g, "").replace(/^[\s\u0000-\u001f\u0085\ufeff]+/, "");
116
126
  if (/^http:/i.test(spelled)) {
117
- throw new Error(`[decidio] refusing to ${hasCredential ? "send credentials" : "send a request"} over plaintext http to ${JSON.stringify(url)} — that is not a complete absolute URL, so where the request would actually connect is undefined (the path is appended to it, which can turn it into a remote host). Set a full https URL, or http://localhost for local dev. Nothing was sent.`);
127
+ throw new Error(`[decidio] refusing to ${hasCredential ? "send credentials" : "send a request"} over plaintext http to ${JSON.stringify(redactUrl(url))} — that is not a complete absolute URL, so where the request would actually connect is undefined (the path is appended to it, which can turn it into a remote host). Set a full https URL, or http://localhost for local dev. Nothing was sent.`);
118
128
  }
119
- throw new Error(`[decidio] the gate URL ${JSON.stringify(url)} is not an absolute URL, so there is no host to send to — where a request would actually connect depends entirely on whichever transport resolves it. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
129
+ throw new Error(`[decidio] the gate URL ${JSON.stringify(redactUrl(url))} is not an absolute URL, so there is no host to send to — where a request would actually connect depends entirely on whichever transport resolves it. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
120
130
  }
121
131
  if (u.protocol !== "http:" && u.protocol !== "https:") {
122
132
  throw new Error(`[decidio] the gate URL uses the ${u.protocol.replace(":", "")} scheme, which is not a Decidio endpoint — use https (or http://localhost for local dev). Nothing was sent.`);
@@ -124,6 +134,17 @@ export function assertSafeTransport(url, hasCredential) {
124
134
  if (u.username || u.password) {
125
135
  throw new Error(`[decidio] the gate URL carries credentials in the URL itself (${u.protocol}//user:***@${u.host}). Decidio authenticates with a bearer token; remove the user:password from the URL. Nothing was sent.`);
126
136
  }
137
+ if (u.hostname === "") {
138
+ throw new Error(`[decidio] the gate URL ${JSON.stringify(redactUrl(url))} has no host — there is nowhere for the request to go. Decidio needs a full https URL (or http://localhost for local dev). Nothing was sent.`);
139
+ }
140
+ {
141
+ const spelled = url.replace(/[\t\r\n]/g, "").replace(/^[\s\u0000-\u001f\u0085\ufeff]+/, "").toLowerCase();
142
+ const origin = `${u.protocol}//${u.host}`.toLowerCase();
143
+ const next = spelled.charAt(origin.length);
144
+ if (!spelled.startsWith(origin) || (next !== "" && next !== "/" && next !== "?" && next !== "#" && next !== ":")) {
145
+ throw new Error(`[decidio] the gate URL ${JSON.stringify(redactUrl(url))} is not written in canonical form — this parser reads it as ${origin}, but the raw spelling is what a transport receives, and a parser that normalizes differently can read it as another host entirely. Write it as ${origin}. Nothing was sent.`);
146
+ }
147
+ }
127
148
  if (u.protocol !== "http:")
128
149
  return;
129
150
  const host = u.hostname.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decidio/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.5",
4
4
  "description": "One-line approval gate for AI-agent actions. Wrap a risky tool call with guard.protect(); Decidio's policy engine decides proceed | route | block, a human approves routed actions, and every outcome is sealed as a verifiable Authority Receipt.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -79,10 +79,10 @@
79
79
  "scripts": {
80
80
  "typecheck": "tsc --noEmit",
81
81
  "build": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsc -p tsconfig.types.json",
82
- "prepublishOnly": "npm run typecheck && npm run build"
82
+ "prepublishOnly": "npm run typecheck && npm run build && node ../publish-boundary/check.mjs --only @decidio/sdk"
83
83
  },
84
84
  "dependencies": {
85
- "@decidio/verify": "0.3.0"
85
+ "@decidio/verify": "0.3.5"
86
86
  },
87
87
  "author": "OmniTwin Technologies Inc.",
88
88
  "bugs": {