@@ -70,6 +70,120 @@ describe("API bearer auth — baseline behavior", () => {
7070 } ) ;
7171} ) ;
7272
73+ describe ( "Packet download authorization" , ( ) => {
74+ // The auth harness has no object store. Reaching its configuration error proves that
75+ // resource lookup and authorization passed and the handler attempted to presign.
76+ const objectStoreError = {
77+ error : "Failed to generate presigned URL: Object store is not configured for protocol: default" ,
78+ } ;
79+
80+ async function seedStoredRuns ( ) {
81+ const seed = await seedTestEnvironment ( server . prisma ) ;
82+ const first = await seedTestRun ( server . prisma , {
83+ environmentId : seed . environment . id ,
84+ projectId : seed . project . id ,
85+ } ) ;
86+ const second = await seedTestRun ( server . prisma , {
87+ environmentId : seed . environment . id ,
88+ projectId : seed . project . id ,
89+ } ) ;
90+
91+ await Promise . all ( [
92+ server . prisma . taskRun . update ( {
93+ where : { id : first . run . id } ,
94+ data : {
95+ payload : `${ first . runFriendlyId } /payload.json` ,
96+ payloadType : "application/store" ,
97+ output : `${ first . runFriendlyId } -1/output.json` ,
98+ outputType : "application/store" ,
99+ } ,
100+ } ) ,
101+ server . prisma . taskRun . update ( {
102+ where : { id : second . run . id } ,
103+ data : {
104+ payload : `${ second . runFriendlyId } /payload.json` ,
105+ payloadType : "application/store" ,
106+ } ,
107+ } ) ,
108+ ] ) ;
109+
110+ return { ...seed , first, second } ;
111+ }
112+
113+ it ( "authorizes run packet downloads against the token's run scope" , async ( ) => {
114+ const { environment, first, second } = await seedStoredRuns ( ) ;
115+ const firstJwt = await generateTestJWT ( environment , {
116+ scopes : [ `read:runs:${ first . runFriendlyId } ` ] ,
117+ } ) ;
118+ const secondJwt = await generateTestJWT ( environment , {
119+ scopes : [ `read:runs:${ second . runFriendlyId } ` ] ,
120+ } ) ;
121+ const asFirst = { headers : { Authorization : `Bearer ${ firstJwt } ` } } ;
122+ const asSecond = { headers : { Authorization : `Bearer ${ secondJwt } ` } } ;
123+
124+ const ownPayload = await server . webapp . fetch (
125+ `/api/v1/runs/${ first . runFriendlyId } /packets/payload` ,
126+ asFirst
127+ ) ;
128+ const ownOutput = await server . webapp . fetch (
129+ `/api/v1/runs/${ first . runFriendlyId } /packets/output` ,
130+ asFirst
131+ ) ;
132+ const otherRun = await server . webapp . fetch (
133+ `/api/v1/runs/${ second . runFriendlyId } /packets/payload` ,
134+ asFirst
135+ ) ;
136+ const inlineOutput = await server . webapp . fetch (
137+ `/api/v1/runs/${ second . runFriendlyId } /packets/output` ,
138+ asSecond
139+ ) ;
140+
141+ expect ( ownPayload . status ) . toBe ( 500 ) ;
142+ expect ( await ownPayload . json ( ) ) . toEqual ( objectStoreError ) ;
143+ expect ( ownOutput . status ) . toBe ( 500 ) ;
144+ expect ( await ownOutput . json ( ) ) . toEqual ( objectStoreError ) ;
145+ expect ( otherRun . status ) . toBe ( 403 ) ;
146+ expect ( inlineOutput . status ) . toBe ( 404 ) ;
147+ } ) ;
148+
149+ it ( "rejects stored-payload pointers from public JWTs on trigger" , async ( ) => {
150+ const { environment } = await seedTestEnvironment ( server . prisma ) ;
151+ const jwt = await generateTestJWT ( environment , { scopes : [ "write:tasks:test-task" ] } ) ;
152+
153+ const res = await server . webapp . fetch ( "/api/v1/tasks/test-task/trigger" , {
154+ method : "POST" ,
155+ headers : { Authorization : `Bearer ${ jwt } ` , "Content-Type" : "application/json" } ,
156+ body : JSON . stringify ( {
157+ payload : "run_victim/payload.json" ,
158+ options : { payloadType : "application/store" } ,
159+ } ) ,
160+ } ) ;
161+
162+ expect ( res . status ) . toBe ( 403 ) ;
163+ } ) ;
164+
165+ it ( "rejects stored-payload pointers from public JWTs on batch trigger" , async ( ) => {
166+ const { environment } = await seedTestEnvironment ( server . prisma ) ;
167+ const jwt = await generateTestJWT ( environment , { scopes : [ "write:tasks:test-task" ] } ) ;
168+
169+ const res = await server . webapp . fetch ( "/api/v2/tasks/batch" , {
170+ method : "POST" ,
171+ headers : { Authorization : `Bearer ${ jwt } ` , "Content-Type" : "application/json" } ,
172+ body : JSON . stringify ( {
173+ items : [
174+ {
175+ task : "test-task" ,
176+ payload : "run_victim/payload.json" ,
177+ options : { payloadType : "application/store" } ,
178+ } ,
179+ ] ,
180+ } ) ,
181+ } ) ;
182+
183+ expect ( res . status ) . toBe ( 403 ) ;
184+ } ) ;
185+ } ) ;
186+
73187describe ( "JWT bearer auth — baseline behavior" , ( ) => {
74188 it ( "valid JWT on JWT-enabled route: auth passes" , async ( ) => {
75189 const { environment } = await seedTestEnvironment ( server . prisma ) ;
0 commit comments