@adversarylabs/sdk 0.1.5 → 0.1.8

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.
@@ -0,0 +1,49 @@
1
+ export declare const ADVERSARY_MANIFEST_FILE_NAME = "adversary.yaml";
2
+ export declare const ADVERSARY_MANIFEST_MAX_BYTES: number;
3
+ export interface DetectionManifest {
4
+ files?: string[];
5
+ entrypoint?: string;
6
+ }
7
+ export interface TriggerManifest {
8
+ manual?: boolean;
9
+ files_changed?: string[];
10
+ }
11
+ export interface RuntimeManifest {
12
+ name?: "node" | "process";
13
+ image?: string;
14
+ version?: string;
15
+ command: string[];
16
+ }
17
+ export interface FilesystemPermissionsManifest {
18
+ read?: string[];
19
+ write?: string[];
20
+ }
21
+ export interface EnvironmentPermissionsManifest {
22
+ allow?: string[];
23
+ }
24
+ export interface PermissionsManifest {
25
+ filesystem?: FilesystemPermissionsManifest;
26
+ network?: boolean;
27
+ environment?: EnvironmentPermissionsManifest;
28
+ enforcement?: "advisory" | "required";
29
+ }
30
+ export interface FindingsManifest {
31
+ format?: "adversary.review.v1";
32
+ }
33
+ export interface AdversaryManifest {
34
+ name: string;
35
+ version?: string;
36
+ description?: string;
37
+ triggers?: TriggerManifest;
38
+ detection?: DetectionManifest;
39
+ runtime: RuntimeManifest;
40
+ permissions?: PermissionsManifest;
41
+ findings?: FindingsManifest;
42
+ }
43
+ export declare class ManifestValidationError extends Error {
44
+ readonly issues: string[];
45
+ constructor(message: string, issues?: string[]);
46
+ }
47
+ export declare function parseAdversaryManifest(source: string | Uint8Array): AdversaryManifest;
48
+ export declare function validateAdversaryManifest(value: unknown): AdversaryManifest;
49
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAC7D,eAAO,MAAM,4BAA4B,QAAU,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,8BAA8B,CAAC;IAC7C,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;CACvC;AAMD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAMD,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO;CAKnD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,iBAAiB,CAiCrF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAO3E"}
@@ -0,0 +1,89 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { Ajv2020 } from "ajv/dist/2020.js";
3
+ import { parseAllDocuments } from "yaml";
4
+ export const ADVERSARY_MANIFEST_FILE_NAME = "adversary.yaml";
5
+ export const ADVERSARY_MANIFEST_MAX_BYTES = 1 << 20;
6
+ export class ManifestValidationError extends Error {
7
+ issues;
8
+ constructor(message, issues = []) {
9
+ super(message);
10
+ this.name = "ManifestValidationError";
11
+ this.issues = issues;
12
+ }
13
+ }
14
+ let manifestValidator;
15
+ export function parseAdversaryManifest(source) {
16
+ const text = typeof source === "string" ? source : new TextDecoder().decode(source);
17
+ const size = Buffer.byteLength(text, "utf8");
18
+ if (size > ADVERSARY_MANIFEST_MAX_BYTES) {
19
+ throw new ManifestValidationError(`adversary.yaml is too large: ${size} bytes exceeds ${ADVERSARY_MANIFEST_MAX_BYTES} bytes`);
20
+ }
21
+ const documents = parseAllDocuments(text, {
22
+ prettyErrors: true,
23
+ uniqueKeys: true,
24
+ });
25
+ if (documents.length !== 1) {
26
+ throw new ManifestValidationError("manifest must contain exactly one YAML document");
27
+ }
28
+ const document = documents[0];
29
+ if (document === undefined || document.contents === null) {
30
+ throw new ManifestValidationError("manifest is empty");
31
+ }
32
+ if (document.errors.length > 0) {
33
+ const issues = document.errors.map((error) => error.message);
34
+ throw new ManifestValidationError(`decode manifest YAML: ${issues.join("; ")}`, issues);
35
+ }
36
+ let value;
37
+ try {
38
+ value = document.toJS({ maxAliasCount: 0 });
39
+ }
40
+ catch (error) {
41
+ const issue = error instanceof Error ? error.message : String(error);
42
+ throw new ManifestValidationError(`decode manifest YAML: ${issue}`, [issue]);
43
+ }
44
+ return validateAdversaryManifest(value);
45
+ }
46
+ export function validateAdversaryManifest(value) {
47
+ const validator = getManifestValidator();
48
+ if (!validator(value)) {
49
+ const issues = (validator.errors ?? []).map(formatSchemaError);
50
+ throw new ManifestValidationError(`invalid adversary manifest: ${issues.join("; ")}`, issues);
51
+ }
52
+ return normalizeLegacyPermissions(value);
53
+ }
54
+ function getManifestValidator() {
55
+ if (manifestValidator !== undefined)
56
+ return manifestValidator;
57
+ const schema = JSON.parse(readFileSync(new URL("../schemas/adversary.manifest.v1.schema.json", import.meta.url), "utf8"));
58
+ manifestValidator = new Ajv2020({
59
+ allErrors: true,
60
+ strict: true,
61
+ }).compile(schema);
62
+ return manifestValidator;
63
+ }
64
+ function formatSchemaError(error) {
65
+ const path = `manifest${error.instancePath.replaceAll("/", ".")}`;
66
+ if (error.keyword === "pattern" && error.schemaPath === "#/$defs/portableProjectPath/pattern") {
67
+ return `${path} must be a portable project-relative path`;
68
+ }
69
+ if (error.keyword === "additionalProperties") {
70
+ return `${path} contains unknown field ${JSON.stringify(error.params.additionalProperty)}`;
71
+ }
72
+ if (error.keyword === "required") {
73
+ return `${path} is missing required field ${JSON.stringify(error.params.missingProperty)}`;
74
+ }
75
+ return `${path} ${error.message ?? `failed ${error.keyword} validation`}`;
76
+ }
77
+ function normalizeLegacyPermissions(manifest) {
78
+ if (manifest.permissions?.env === undefined)
79
+ return manifest;
80
+ const { env, ...permissions } = manifest.permissions;
81
+ return {
82
+ ...manifest,
83
+ permissions: {
84
+ ...permissions,
85
+ environment: { allow: env },
86
+ },
87
+ };
88
+ }
89
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { Ajv2020, type ErrorObject, type ValidateFunction } from \"ajv/dist/2020.js\";\nimport { parseAllDocuments } from \"yaml\";\n\nexport const ADVERSARY_MANIFEST_FILE_NAME = \"adversary.yaml\";\nexport const ADVERSARY_MANIFEST_MAX_BYTES = 1 << 20;\n\nexport interface DetectionManifest {\n files?: string[];\n entrypoint?: string;\n}\n\nexport interface TriggerManifest {\n manual?: boolean;\n files_changed?: string[];\n}\n\nexport interface RuntimeManifest {\n name?: \"node\" | \"process\";\n image?: string;\n version?: string;\n command: string[];\n}\n\nexport interface FilesystemPermissionsManifest {\n read?: string[];\n write?: string[];\n}\n\nexport interface EnvironmentPermissionsManifest {\n allow?: string[];\n}\n\nexport interface PermissionsManifest {\n filesystem?: FilesystemPermissionsManifest;\n network?: boolean;\n environment?: EnvironmentPermissionsManifest;\n enforcement?: \"advisory\" | \"required\";\n}\n\ninterface LegacyPermissionsManifest extends PermissionsManifest {\n env?: string[];\n}\n\nexport interface FindingsManifest {\n format?: \"adversary.review.v1\";\n}\n\nexport interface AdversaryManifest {\n name: string;\n version?: string;\n description?: string;\n triggers?: TriggerManifest;\n detection?: DetectionManifest;\n runtime: RuntimeManifest;\n permissions?: PermissionsManifest;\n findings?: FindingsManifest;\n}\n\ninterface ParsedAdversaryManifest extends Omit<AdversaryManifest, \"permissions\"> {\n permissions?: LegacyPermissionsManifest;\n}\n\nexport class ManifestValidationError extends Error {\n readonly issues: string[];\n\n constructor(message: string, issues: string[] = []) {\n super(message);\n this.name = \"ManifestValidationError\";\n this.issues = issues;\n }\n}\n\nlet manifestValidator: ValidateFunction<ParsedAdversaryManifest> | undefined;\n\nexport function parseAdversaryManifest(source: string | Uint8Array): AdversaryManifest {\n const text = typeof source === \"string\" ? source : new TextDecoder().decode(source);\n const size = Buffer.byteLength(text, \"utf8\");\n if (size > ADVERSARY_MANIFEST_MAX_BYTES) {\n throw new ManifestValidationError(\n `adversary.yaml is too large: ${size} bytes exceeds ${ADVERSARY_MANIFEST_MAX_BYTES} bytes`,\n );\n }\n\n const documents = parseAllDocuments(text, {\n prettyErrors: true,\n uniqueKeys: true,\n });\n if (documents.length !== 1) {\n throw new ManifestValidationError(\"manifest must contain exactly one YAML document\");\n }\n const document = documents[0];\n if (document === undefined || document.contents === null) {\n throw new ManifestValidationError(\"manifest is empty\");\n }\n if (document.errors.length > 0) {\n const issues = document.errors.map((error) => error.message);\n throw new ManifestValidationError(`decode manifest YAML: ${issues.join(\"; \")}`, issues);\n }\n\n let value: unknown;\n try {\n value = document.toJS({ maxAliasCount: 0 });\n } catch (error) {\n const issue = error instanceof Error ? error.message : String(error);\n throw new ManifestValidationError(`decode manifest YAML: ${issue}`, [issue]);\n }\n return validateAdversaryManifest(value);\n}\n\nexport function validateAdversaryManifest(value: unknown): AdversaryManifest {\n const validator = getManifestValidator();\n if (!validator(value)) {\n const issues = (validator.errors ?? []).map(formatSchemaError);\n throw new ManifestValidationError(`invalid adversary manifest: ${issues.join(\"; \")}`, issues);\n }\n return normalizeLegacyPermissions(value);\n}\n\nfunction getManifestValidator(): ValidateFunction<ParsedAdversaryManifest> {\n if (manifestValidator !== undefined) return manifestValidator;\n const schema = JSON.parse(\n readFileSync(new URL(\"../schemas/adversary.manifest.v1.schema.json\", import.meta.url), \"utf8\"),\n );\n manifestValidator = new Ajv2020({\n allErrors: true,\n strict: true,\n }).compile<ParsedAdversaryManifest>(schema);\n return manifestValidator;\n}\n\nfunction formatSchemaError(error: ErrorObject): string {\n const path = `manifest${error.instancePath.replaceAll(\"/\", \".\")}`;\n if (error.keyword === \"pattern\" && error.schemaPath === \"#/$defs/portableProjectPath/pattern\") {\n return `${path} must be a portable project-relative path`;\n }\n if (error.keyword === \"additionalProperties\") {\n return `${path} contains unknown field ${JSON.stringify(error.params.additionalProperty)}`;\n }\n if (error.keyword === \"required\") {\n return `${path} is missing required field ${JSON.stringify(error.params.missingProperty)}`;\n }\n return `${path} ${error.message ?? `failed ${error.keyword} validation`}`;\n}\n\nfunction normalizeLegacyPermissions(manifest: ParsedAdversaryManifest): AdversaryManifest {\n if (manifest.permissions?.env === undefined) return manifest;\n const { env, ...permissions } = manifest.permissions;\n return {\n ...manifest,\n permissions: {\n ...permissions,\n environment: { allow: env },\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversarylabs/sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "Small TypeScript SDK for authoring Adversaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.js"
24
24
  },
25
+ "./schemas/adversary.manifest.v1": "./schemas/adversary.manifest.v1.schema.json",
25
26
  "./schemas/adversary.input.v1": "./schemas/adversary.input.v1.schema.json",
26
27
  "./schemas/adversary.review.v1": "./schemas/adversary.review.v1.schema.json"
27
28
  },
@@ -44,6 +45,7 @@
44
45
  "vitest": "^4.1.10"
45
46
  },
46
47
  "dependencies": {
47
- "ajv": "^8.20.0"
48
+ "ajv": "^8.20.0",
49
+ "yaml": "^2.8.1"
48
50
  }
49
51
  }
@@ -16,6 +16,37 @@
16
16
  "minLength": 1
17
17
  }
18
18
  }
19
+ },
20
+ "change": {
21
+ "type": ["object", "null"],
22
+ "additionalProperties": true,
23
+ "description": "The requested review scope. Null or absent when the runner asked for a full-target review without a change context.",
24
+ "properties": {
25
+ "type": {
26
+ "type": "string",
27
+ "description": "Change representation reported by the runner; \"diff\" today."
28
+ },
29
+ "base_ref": {
30
+ "type": "string",
31
+ "description": "Base revision of the reviewed change."
32
+ },
33
+ "head_ref": {
34
+ "type": "string",
35
+ "description": "Head revision of the reviewed change, or the WORKTREE sentinel for the uncommitted worktree."
36
+ },
37
+ "scan_mode": {
38
+ "type": "string",
39
+ "enum": ["changed", "all"],
40
+ "description": "\"changed\" restricts review to the change; \"all\" requests the entire target."
41
+ },
42
+ "changed_files": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": "string"
46
+ },
47
+ "description": "Repository-relative paths the runner identified as changed."
48
+ }
49
+ }
19
50
  }
20
51
  }
21
52
  }
@@ -0,0 +1,159 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adversary.dev/schemas/adversary.manifest.v1.schema.json",
4
+ "title": "Adversary Manifest v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["name", "runtime"],
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "pattern": "^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?(?:/[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?)*$"
12
+ },
13
+ "version": {
14
+ "type": "string",
15
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$"
16
+ },
17
+ "description": { "type": "string" },
18
+ "triggers": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "manual": { "type": "boolean" },
23
+ "files_changed": {
24
+ "type": "array",
25
+ "items": { "$ref": "#/$defs/normalizedString" }
26
+ }
27
+ }
28
+ },
29
+ "detection": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "minProperties": 1,
33
+ "properties": {
34
+ "files": {
35
+ "type": "array",
36
+ "minItems": 1,
37
+ "uniqueItems": true,
38
+ "items": { "$ref": "#/$defs/normalizedString" }
39
+ },
40
+ "entrypoint": { "$ref": "#/$defs/portableProjectPath" }
41
+ }
42
+ },
43
+ "runtime": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "required": ["command"],
47
+ "oneOf": [
48
+ {
49
+ "required": ["image"],
50
+ "properties": {
51
+ "image": true,
52
+ "name": false,
53
+ "version": false
54
+ }
55
+ },
56
+ {
57
+ "required": ["name", "version"],
58
+ "properties": {
59
+ "name": true,
60
+ "version": true,
61
+ "image": false
62
+ }
63
+ }
64
+ ],
65
+ "properties": {
66
+ "name": { "enum": ["node", "process"] },
67
+ "image": {
68
+ "type": "string",
69
+ "pattern": "^(?:(?:localhost|\\[[0-9A-Fa-f:]+\\]|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*)(?::[0-9]{1,5})?/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?(?:@(?:sha256:[a-f0-9]{64}|sha384:[a-f0-9]{96}|sha512:[a-f0-9]{128}))?$"
70
+ },
71
+ "version": {
72
+ "type": "string",
73
+ "pattern": "^(?:node@|v)?(?:[0-9]+(?:\\.[0-9]+){0,2}|[<>=~^*xX0-9., |+\\-]+)$"
74
+ },
75
+ "command": {
76
+ "type": "array",
77
+ "minItems": 1,
78
+ "items": { "$ref": "#/$defs/normalizedString" }
79
+ }
80
+ }
81
+ },
82
+ "permissions": {
83
+ "type": "object",
84
+ "additionalProperties": false,
85
+ "properties": {
86
+ "filesystem": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "read": {
91
+ "type": "array",
92
+ "uniqueItems": true,
93
+ "items": { "$ref": "#/$defs/projectPath" }
94
+ },
95
+ "write": {
96
+ "type": "array",
97
+ "uniqueItems": true,
98
+ "items": { "$ref": "#/$defs/projectPath" }
99
+ }
100
+ }
101
+ },
102
+ "network": { "type": "boolean" },
103
+ "environment": {
104
+ "type": "object",
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "allow": {
108
+ "type": "array",
109
+ "uniqueItems": true,
110
+ "items": {
111
+ "type": "string",
112
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
113
+ }
114
+ }
115
+ }
116
+ },
117
+ "env": {
118
+ "description": "Deprecated alias for environment.allow.",
119
+ "deprecated": true,
120
+ "type": "array",
121
+ "uniqueItems": true,
122
+ "items": {
123
+ "type": "string",
124
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
125
+ }
126
+ },
127
+ "enforcement": { "enum": ["advisory", "required"] }
128
+ },
129
+ "not": {
130
+ "properties": {
131
+ "environment": true,
132
+ "env": true
133
+ },
134
+ "required": ["environment", "env"]
135
+ }
136
+ },
137
+ "findings": {
138
+ "type": "object",
139
+ "additionalProperties": false,
140
+ "properties": {
141
+ "format": { "const": "adversary.review.v1" }
142
+ }
143
+ }
144
+ },
145
+ "$defs": {
146
+ "normalizedString": {
147
+ "type": "string",
148
+ "pattern": "^[^\\x00\\s](?:[^\\x00]*[^\\x00\\s])?$"
149
+ },
150
+ "portableProjectPath": {
151
+ "type": "string",
152
+ "pattern": "^(?!\\s)(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!(?:\\.{1,2})(?:/|$))(?!.*(?:/\\.{1,2})(?:/|$))(?!.*//)(?!.*\\s$)[^/]+(?:/[^/]+)*$"
153
+ },
154
+ "projectPath": {
155
+ "type": "string",
156
+ "pattern": "^(?:\\.|[^./:\\\\x00\\s](?:[^/:\\\\x00]*[^/:\\\\x00\\s])?|\\.[^./:\\\\x00\\s](?:[^/:\\\\x00]*[^/:\\\\x00\\s])?)(?:/(?:[^./:\\\\x00\\s](?:[^/:\\\\x00]*[^/:\\\\x00\\s])?|\\.[^./:\\\\x00\\s](?:[^/:\\\\x00]*[^/:\\\\x00\\s])?))*$"
157
+ }
158
+ }
159
+ }
@@ -2,6 +2,11 @@
2
2
 
3
3
  Minimal TypeScript Adversary built with `@adversarylabs/sdk`.
4
4
 
5
+ ## Automatic detection
6
+
7
+ `adversary auto` selects this starter when TypeScript files change. Adjust `detection.files` in
8
+ `adversary.yaml` to match the technology or domain your adversary reviews.
9
+
5
10
  ```bash
6
11
  npm ci
7
12
  npm test
@@ -8,6 +8,11 @@ triggers:
8
8
  - "*"
9
9
  - "**/*"
10
10
 
11
+ detection:
12
+ files:
13
+ - "*.ts"
14
+ - "**/*.ts"
15
+
11
16
  runtime:
12
17
  name: node
13
18
  version: "22"
@@ -21,7 +26,8 @@ permissions:
21
26
  write:
22
27
  - .adversary/results
23
28
  network: false
24
- env: []
29
+ environment:
30
+ allow: []
25
31
 
26
32
  findings:
27
33
  format: adversary.review.v1